
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	epsfparser.m
 *
 *	ReadEpsf() is the procedure to invoke for parsing. It takes a pointer 
 *	to a structure as an argument. This structure contains the pointer to
 *	the data as well as the locations to place the results of the parsing.
 *	This parser grabs the bounding box and the fonts. Other parsers
 *	can add to this general structure to do more sophisticated parsing
 *	such as page breakout and such.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-19-91		Added this comment.
 */
 
#import "epsfstruct.h"

#import <appkit/nextstd.h>
#import <objc/hashtable.h>
#import <objc/List.h>
#import <objc/Storage.h>
#import <strings.h>

static int	processToField(EpsfStruct *epsf);
static int  processToLineEnd(EpsfStruct *epsf);

static int  processBBox(EpsfStruct *epsf);
static int  processIncludeFile(EpsfStruct *epsf);
static int  processToEndDocument(EpsfStruct *epsf);
static int  processDocumentResource(EpsfStruct *epsf);
static int  processPageCount(EpsfStruct *epsf);

/*
*	These static CommentHandle arrays are organized
*	into different groups according to the level of the
*	parsing. The general comments handle the top most
*	level of parsing. The document comments handle 
*	the parsing of an included document.
*	
*	This setup allows the same general flow to handle the 
*	parsing of each group. When a comment is reached
*	that indicates another level of  parsing should be
*	performed, the tables for that level is passed to the 
*	processToComment procedure.
*
*	Others group could be added (not necessarily for
*	Epsf inclusion but for print management parsing). One
*	example is a page comment group. This group would
*	parse the page comments breaking out the location
*	of the individual pages as well as fonts, colors, paper
*	sizes, etc.
*
*/

typedef struct {
	char *comment;		int (*proc) (EpsfStruct *epsf);
} CommentHandle;

/*****   General comments   *****/
static CommentHandle generalComments [ ] =
{
	{"%%BoundingBox:",	processBBox},
	{"%%BeginDocument",	processToEndDocument},
	{"%%Document",		processDocumentResource},
	{"%%Pages:",			processPageCount}
};

/*****   Document comments   *****/
static CommentHandle documentIncludeComments [ ] =
{
	{"%%BeginDocument",			processToEndDocument},
	{"%%IncludeFile",				processIncludeFile}
};

/*****   Document comments   *****/
static CommentHandle documentEndComments [ ] =
{
	{"%%EndDocument",		processToLineEnd}
};


/*
*	These static arrays are used to recognize the types of resources.
*	For starters there are at present 5 types of resources - fonts, files,
*	procsets, patterns and forms. Within each type, there are 3 states -
*	present, needed and supplied. The present state is the union of the
*	needed and supplied. The needed means the resource is not included
*	with the document. The supplied means the resource is included with
*	the document.
*/

typedef struct {
	char *comment;		int  key;
} ResourceHandle;

/*****   Resource comments   *****/
static ResourceHandle resourceStates [ ] =
{
	{"Needed",				RES_NEEDED},
	{"Supplied",				RES_SUPPLIED}
};

static ResourceHandle resourceTypeMain [ ] =
{
	{"Fonts",					RES_FONTS},
	{"Files",					RES_FILES},
	{"ProcSets",				RES_PROCSETS},
	{"Patterns",				RES_PATTERNS},
	{"Forms",					RES_FORMS},	
	{"Resources",				RES_RESOURCES},
};

static ResourceHandle resourceTypeSub [ ] =
{
	{"fonts",					RES_FONTS},
	{"files",					RES_FILES},
	{"procsets",				RES_PROCSETS},
	{"patterns",				RES_PATTERNS},
	{"forms",					RES_FORMS}
};

/*
*	This is the workhorse of the parser. It takes a general structure
*	and some pointers to CommentHandle arrays and processes
*	the data until the one of the comments in the endTable has
*	been reached or the data runs out.  Any matches to the 
*	processTable comments processes the comment without
*	returning. (Any match to the endTable returns after processing.)
*
*	If endLength is 0 then we will only process until the next line that
*	is not a comment.
*/
static int processToComment(EpsfStruct *epsf, CommentHandle endTable[ ], int endLength,
		CommentHandle processTable[ ], int processLength)
{
	int		i, j;

	while (epsf->data < epsf->enddata)
	{
		if (strncmp(epsf->data, "%", 1) == 0)
		{
			for (j = 0; j < endLength; j++)
			{
				if (strncmp(epsf->data, endTable[j].comment,
					strlen(endTable[j].comment)) == 0)
				{
					return endTable[j].proc(epsf);
				}
			}

			for (i = 0; i < processLength; i++)
			{
				if (strncmp(epsf->data, processTable[i].comment,
					strlen(processTable[i].comment)) == 0)
				{
					processTable[i].proc(epsf);
					break;
				}
			}

			if (i == processLength)
				processToLineEnd(epsf);		
		}
		else
		{
		 	/* If endLength == 0 then process until the next non-comment. */
		 	if (endLength == 0)
				return EPSF_OK;
			else
				processToLineEnd(epsf);
		}
	}

	return EPSF_OK;
}

static int CheckEpsf(EpsfStruct *epsf)
{	
	int		rc = EPSF_INVALIDPS;

	if (strncmp(epsf->data, "%!PS-Adobe-", 11) == 0)
	{
		processToField(epsf);
		if (strncmp(epsf->data, "EPSF-", 5) == 0)
		{
			processToLineEnd(epsf);
			rc = EPSF_OK;
		}
	}
	
	return rc;
}

void  CheckEpsfBinaryHeader(char **data, int *len)
{
	EpsfBinaryHeader		*header;

	char		*newdata;

	int		newlen;
	
	/*
	*	If a binary header file is encountered then just skip
	*	to the PostScript section of the file. Update the
	*	data and len arguments to reflect this change.
	*/
	header = (EpsfBinaryHeader *) *data;
	if (header->idbytes == EPSF_BINARYID)
	{
		newdata = *data + header->ps_start;
		newlen = header->ps_length;

		if (newdata >= *data && newdata + newlen <= *data + *len)
		{
			*data = newdata;
			*len = newlen;
		}
	}
}

/*
*	This is the routine invoked for parsing. It validates the file
*	as a proper EPSF file (by checking for the %!PS-Adobe or
*	for the binary header id) and then parses the file.
*/
int  ReadEpsf(EpsfStruct *epsf)
{
	int		rc = EPSF_OK;

	if (epsf && epsf->filedata && *epsf->filedata)
	{
		CheckEpsfBinaryHeader(&epsf->filedata, &epsf->filelen);
		epsf->data = epsf->filedata;
		epsf->enddata = epsf->filedata + epsf->filelen;

		if (epsf->data < epsf->enddata && !CheckEpsf(epsf))
		{
			bzero(epsf->bbox, sizeof(epsf->bbox));
			bzero(&epsf->resources, sizeof(epsf->resources));
			bzero(&epsf->inclusions, sizeof(epsf->inclusions));

			while (epsf->data < epsf->enddata && !rc)
			{
				rc = processToComment(epsf,
					generalComments, sizeof(generalComments)/sizeof(CommentHandle),
					NULL, 0); 
			}

			/* Check for a valid bounding box. */
			if (!rc && epsf->bbox[0] == 0 && epsf->bbox[1] == 0 &&
				epsf->bbox[2] == 0 && epsf->bbox[3] == 0)
				rc = EPSF_INVALIDBBOX;
		}
		else
			rc =  EPSF_INVALIDPS;
	}
	else
		rc =  EPSF_INVALIDPS;

	return rc;
}

static int processToNonSpace(EpsfStruct *epsf)
{
	while (epsf->data < epsf->enddata && *epsf->data == ' ')
		epsf->data++;
	
	return EPSF_OK;
}

static int processToField(EpsfStruct *epsf)
{

	while (epsf->data < epsf->enddata && *epsf->data != ':' && *epsf->data != ' ')
		epsf->data++;
	if (epsf->data < epsf->enddata && *epsf->data == ':')
		epsf->data++;
	processToNonSpace(epsf);
	
	return EPSF_OK;
}

static int processToLineEnd(EpsfStruct  *epsf)
{
	while (epsf->data < epsf->enddata && *epsf->data != '\n')
	 	epsf->data++;
	if (epsf->data < epsf->enddata)
		epsf->data++;
	
	return EPSF_OK;
}

static int processBBox(EpsfStruct *epsf)
{

	int		rc = EPSF_OK;
	
	processToField(epsf);
	if (epsf->data < epsf->enddata && strncmp(epsf->data, "(atend)", 7) != 0)
		if ((sscanf(epsf->data, "%f %f %f %f", &epsf->bbox[0], &epsf->bbox[1],
			&epsf->bbox[2], &epsf->bbox[3]) != 4) ||
			epsf->bbox[2] <= epsf->bbox[0] || epsf->bbox[3] <= epsf->bbox[1])
			rc = EPSF_INVALIDBBOX;
	
	processToLineEnd(epsf);
	
	return rc;
}

static int processIncludeFile(EpsfStruct *epsf)
{
	int			rc = EPSF_OK;

	char			name[EPSF_MAXLINE];

	Inclusion		inclusion;

	bzero(&inclusion, sizeof(inclusion));

	inclusion.offset = epsf->data - epsf->filedata;
	processToField(epsf);
	if (epsf->data < epsf->enddata)
	 {
		if (sscanf(epsf->data, "%s", name))
			inclusion.filename = NXUniqueString(name);

		processToLineEnd(epsf);
		inclusion.len = (epsf->data - epsf->filedata) - inclusion.offset;

		if (inclusion.filename)
		{	
			if (!epsf->inclusions)
				epsf->inclusions= [Storage  newCount:0 elementSize:sizeof(Inclusion)  					description:InclusionDescription];
			
			[epsf->inclusions  addElement:&inclusion];
		}
	}
	else
		rc = EPSF_INVALIDDOCUMENT;

	return rc;
}

static int processToEndDocument(EpsfStruct *epsf)
{
	processToLineEnd(epsf);
	processToComment(epsf,
		documentEndComments,
			sizeof(documentEndComments)/sizeof(CommentHandle),
		documentIncludeComments,
			sizeof(documentIncludeComments)/sizeof(CommentHandle));

	if (epsf->data >= epsf->enddata)
		return EPSF_INVALIDDOCUMENT;
	else
		return EPSF_OK;
}

/* Checks the page count. If greater than 1, an error is returned. */
/* Eps files should be single page documents only. */
static int processPageCount(EpsfStruct *epsf)
{
	int		rc = EPSF_OK;

	int		pagetotal = 0;

	processToField(epsf);
	if (epsf->data < epsf->enddata && strncmp(epsf->data, "(atend)", 7) != 0)
		if (sscanf(epsf->data, "%d", &pagetotal))
			if (pagetotal > 1)
				rc = EPSF_INVALIDPAGECOUNT;
	
	processToLineEnd(epsf);
	
	return rc;
}

static int checkResource(EpsfStruct *epsf, ResourceHandle table[ ], int length)
{
	int		i;

	for (i = 0; i < length; i++)
	{
		if (epsf->data < epsf->enddata &&
			strncmp(epsf->data, table[i].comment, strlen(table[i].comment)) == 0)
		{
			epsf->data += strlen(table[i].comment);
			return table[i].key;
		}
	}

	return -1;
}	

static int processResourceLine(EpsfStruct *epsf, int type, int state)
 {
 	int			i;

	char			name[EPSF_MAXLINE], temp[40];

	if (!epsf->resources[type].states[state])
		epsf->resources[type].states[state] = [List  new];

	processToNonSpace(epsf);
	while (epsf->data < epsf->enddata && *epsf->data != '\n')
	 {
		sscanf(epsf->data, "%s", name);
		epsf->data += strlen(name);
		processToNonSpace(epsf);
	 	if (type == RES_PROCSETS)
		{
			for (i = 0; i < 2 && epsf->data < epsf->enddata && *epsf->data != '\n'; i++)
			{
				if (sscanf(epsf->data, "%s", temp))
				{
					strcat(name, " ");
					strcat(name, temp);
					epsf->data += strlen(temp);
					processToNonSpace(epsf);
				}
			}
		}
		[epsf->resources[type].states[state]
				addObjectIfAbsent:(id) NXUniqueString(name)];
	}

	return EPSF_OK;
}

static int processResources(EpsfStruct *epsf, int  xtype, int state)
{
	int		done = FALSE, type;

	type = xtype;
	processToField(epsf);
	if (epsf->data < epsf->enddata && strncmp(epsf->data, "(atend)", 7) != 0)
	{
		while (epsf->data < epsf->enddata && !done )
		{
			processToNonSpace(epsf);
			 if (*epsf->data == '\n')
			 {
			 	epsf->data++;
				if (epsf->data < epsf->enddata && strncmp(epsf->data, "%%+", 3) == 0)
					epsf->data += 3;
				else
					done = TRUE;
			}
			else
			{
				if (xtype == RES_RESOURCES)
					type = checkResource(epsf, resourceTypeSub,
							sizeof(resourceTypeSub)/sizeof(ResourceHandle));

				if (type >= RES_FONTS && type <= RES_FORMS)
					processResourceLine(epsf, type, state);
				else
					while (epsf->data < epsf->enddata && *epsf->data != '\n')
						epsf->data++;
			}
		}
	}
	else
		processToLineEnd(epsf);

	return EPSF_OK;	
}

static int processDocumentResource(EpsfStruct *epsf)
{
	int			state, type;

	epsf->data += 10;
	if (epsf->data < epsf->enddata)
	{
		state = checkResource(epsf, resourceStates,
					sizeof(resourceStates)/sizeof(ResourceHandle));
		state = MAX(RES_PRESENT, state);
		type = checkResource(epsf, resourceTypeMain,
					sizeof(resourceTypeMain)/sizeof(ResourceHandle));
		if (type >= 0)
			processResources(epsf, type, state);
	}

	return EPSF_OK;
}

static char *getResource(int  key, ResourceHandle table[ ], int length)
{
	int		i;

	for (i = 0; i < length; i++)
		if (table[i].key == key)
			return table[i].comment;

	return NULL;
}	

void GetDocumentComment(char *comment, int type, int state)
{
	char		*subOne, *subTwo;

	strcpy(comment, "%%%%Document");
	subOne = getResource(state, resourceStates,
				sizeof(resourceStates)/sizeof(ResourceHandle));
	if (subOne)
		strcat(comment, subOne);
	subTwo = getResource(type, resourceTypeMain,
				sizeof(resourceTypeMain)/sizeof(ResourceHandle));
	if (subTwo)
		strcat(comment, subTwo);
	strcat(comment, ": ");
}

const char *GetTypeComment(int type)
{
	return getResource(type, resourceTypeMain,
				sizeof(resourceTypeMain)/sizeof(ResourceHandle));
}

	
